package client.network;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Set;

import client.gameplay.Console;

import network.DataHandleListener;
import network.Listener;
import network.ReadData;
import network.SendData;

/**
 * @author Erik
 * The main-network-part of the client
 *  */

public class Client extends Listener implements Runnable {
	private SocketChannel client;
	private InetSocketAddress isa;
	private String address;
	private int port;

	private ReadData reader;
	private SendData sender;
	private Console console;


	/**
	 * The function that starts the networkThread in the client
	 */
	public void run() {
		try {
			console.println("Connecting to: " + address + " on port: " + port + " ...");
			connect(address, port);
		} catch (Exception e) {
			console.println("[Warning]: " + e.getMessage());
			e.printStackTrace();
		}
		console.println("[Warning]: The server timed out");
	}

	public Client(String address, int port, Console console) {
		this.port = port;
		this.address = address;
		this.console = console;
		reader = new ReadData();
		sender = new SendData();
		//extended from Listener
		listener = new ArrayList<DataHandleListener>();
	}

	/**
	 * Connect to address/port. if there is no connection made in 500ms, something went
	 * wrong with the connection.
	 * This function receive data continuous from the server and tells all listeners that is interested
	 * that there is data to handle
	 * @param address
	 * @param port
	 * @throws IOException
	 */
	private void connect(String address, int port) throws IOException {
		client = SocketChannel.open();
		isa = new InetSocketAddress(address, port);
		Selector sel = Selector.open();

		client.configureBlocking(false);
		if(!client.connect(isa))
		// Record to selector (OP_CONNECT type)
		client.register(sel, SelectionKey.OP_CONNECT | SelectionKey.OP_READ
				| SelectionKey.OP_WRITE);

		// wait 500 ms for the server
		while (sel.select(5000) > 0) {
			// Get keys
			Set<SelectionKey> keys = sel.selectedKeys();
			Iterator<SelectionKey> itr = keys.iterator();

			while (itr.hasNext()) {
				SelectionKey key = (SelectionKey) itr.next();

				// Remove the current key
				itr.remove();

				// Attempt a connection
				if (key.isConnectable()) {
					// Connection OK
					// Close pendent connections
					if (client.isConnectionPending())
						client.finishConnect();
				}

				else if (key.isReadable()) {
					String dataPackage = null;
					try {
						dataPackage = reader.read(client);
						// something went wrong with the client, disconnect the
						// client
					} catch (Exception e) {
						console.println("[Warning]: " + e.getMessage());
						e.printStackTrace();
						disconnect();
					}

					if (dataPackage != null) {
						notifyListener(null, dataPackage,
								reader.getPackageID(client));
					}
				}

				else if (key.isWritable()) {
					if(!sendToServer())
						disconnect(); //socket error, disconnect
				}
			}
		}
	}
	
	public boolean sendToServer() throws IOException {
		return sender.send(client);
	}
	
	/**
	 * Send data to the server
	 * @param data
	 * @param packageID
	 * @return
	 */
	public boolean sendToServer(String data, int packageID) {
		return sender.send(client, data, packageID);
	}

	public void disconnect() {
		try {
			client.close();
			console.println("Socket closed");
		} 
		catch (IOException e) {}
	}
}